package gateway.server.route;

import gateway.api.annotation.GatewayType;
import gateway.api.aware.ArgsAware;
import gateway.api.aware.InitializingAware;
import gateway.api.engine.Connector;
import gateway.api.engine.Filter;
import gateway.api.engine.Inbound;
import gateway.api.engine.Outbound;
import gateway.config.Config;
import gateway.config.IdConfig;
import gateway.config.RouteConfig;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * @author Jin Zheng
 * @since 1.0 2020-09-11
 */
public class RouteInitializer
{
	private static Logger logger = LoggerFactory.getLogger(RouteInitializer.class);
	private final RouteManagement routeManagement;
	private final RouteLoader loader = new RouteLoader();

	public RouteInitializer(RouteManagement routeManagement)
	{
		this.routeManagement = routeManagement;
	}

	public void setup(Config config)
	{
		Map<String, RouteItem<Connector>> connectorMap = this.initRouteItem(config.getConnectorConfigs(), Connector.class);
		for (Map.Entry<String, RouteItem<Connector>> entry : connectorMap.entrySet())
		{
			Connector connector = entry.getValue().getTarget();
			connector.setId(entry.getKey());
			routeManagement.addConnector(connector);
		}

		Map<String, RouteItem<Inbound>> inboundMap = this.initRouteItem(config.getInboundConfigs(), Inbound.class);
		Map<String, RouteItem<Filter>> filterMap = this.initRouteItem(config.getFilterConfigs(), Filter.class);
		Map<String, RouteItem<Outbound>> outboundMap = this.initRouteItem(config.getOutboundConfigs(), Outbound.class);

		for (RouteConfig routeConfig : config.getRouteConfigs())
		{
			String connectorId = routeConfig.connector().id();
			RouteItem<Connector> connector = connectorMap.get(connectorId);
			if (connector == null)
				throw new IllegalArgumentException("Connector not exist, id: " + connectorId);

			RouteItem<Inbound> inbound = this.getRouteItem(inboundMap, connector.getType(), routeConfig.inbound(), Inbound.class);
			List<RouteItem<Filter>> filters = this.getRouteItemList(filterMap, connector.getType(), routeConfig.filters(), Filter.class);
			RouteItem<Outbound> outbound = this.getRouteItem(outboundMap, null, routeConfig.outbound(), Outbound.class);

			Route route = new Route(routeConfig.id());
			route.setOrder(routeConfig.order());
			route.setArgs(routeConfig.args());
			route.setInbound(inbound);
			route.setFilterList(filters);
			route.setOutbound(outbound);
			routeManagement.add(connectorId, route);
		}
	}

	private <T extends ArgsAware, V extends IdConfig> List<RouteItem<T>> getRouteItemList(
			Map<String, RouteItem<T>> map, GatewayType type, List<V> configList, Class<T> clazz)
	{
		List<RouteItem<T>> list = new ArrayList<>();
		for (V config : configList)
		{
			RouteItem<T> target = this.getRouteItem(map, type, config, clazz);
			list.add(target);
		}
		return list;
	}

	private <T extends ArgsAware, V extends IdConfig> RouteItem<T> getRouteItem(
			Map<String, RouteItem<T>> map, GatewayType type, V config, Class<T> clazz)
	{
		if (config.isInline())
		{
			return this.loadRouteItem(config, clazz);
		}

		RouteItem<T> item = map.get(config.id());
		if (item == null)
			throw new IllegalArgumentException(clazz.getSimpleName() + " not exist, id: " + config.id());
		if (type != null && item.getType() != GatewayType.ANY && item.getType() != type)
			throw new IllegalArgumentException(String.format("Connector type is %s, but %s type is %s", type, clazz.getSimpleName(), item.getType()));

		return item;
	}

	private <T extends ArgsAware, V extends IdConfig> Map<String, RouteItem<T>> initRouteItem(List<V> configList, Class<T> clazz)
	{
		Map<String, RouteItem<T>> map = new HashMap<>();
		for (V config : configList)
		{
			RouteItem<T> item = this.loadRouteItem(config, clazz);
			map.put(config.id(), item);
		}
		return map;
	}

	private <T extends ArgsAware, V extends IdConfig> RouteItem<T> loadRouteItem(V config, Class<T> clazz)
	{
		RouteItem<T> item = loader.load(config.name(), clazz);
		if (item == null)
			throw new IllegalArgumentException(clazz.getSimpleName() + " not exist, name: " + config.name());

		item.getTarget().setArgs(config.args());
		if (logger.isDebugEnabled())
		{
			logger.debug("Loaded, name: {}, type: {}, args: {}", config.name(), item.getType(), config.args());
		}
		routeManagement.addObject(item.getTarget());
		return item;
	}

	private void initAware(ArgsAware aware)
	{
		if (aware instanceof InitializingAware)
		{
			try
			{
				((InitializingAware)aware).initialize();
			}
			catch (Exception e)
			{
				logger.error("Initialize error, ", e);
			}
		}
	}

}
